#include "fasterdom4fasterjson.h"

#define __DEBUG			0

#define TRACE_FASTERJSON_PARAMETERS(_s_) \
	printf( "%s - jpath[%.*s] node[%.*s] content[%.*s]\n" , (_s_) , jpath_len,jpath , node_len,node , content_len,content ); \

struct ParseContext
{
	struct DomNode	*root_node ;
	struct DomNode	*current_parent_node ;
} ;

static inline int CallbackOnEnterJsonBranch( int type , char *jpath , int jpath_len , int jpath_size , char *node , int node_len , char *content , int content_len , void *p )
{
	struct ParseContext	*p_parse_ctx = (struct ParseContext *)p ;
	
#if __DEBUG
	TRACE_FASTERJSON_PARAMETERS( "CallbackOnEnterJsonBranch" )
#endif
	
	if( p_parse_ctx->root_node == NULL )
	{
		p_parse_ctx->current_parent_node = CreateDomNode( node , node_len , DOMNODE_TYPE_BRANCH , NULL , 0 ) ;
		if( p_parse_ctx->current_parent_node == NULL )
		{
			return -11;
		}
		p_parse_ctx->root_node = p_parse_ctx->current_parent_node ;
	}
	else
	{
		struct DomNode	*current_parent_node = NULL ;
		
		current_parent_node = AddChildDomNode_BRANCH( p_parse_ctx->current_parent_node , node , node_len ) ;
		if( current_parent_node == NULL )
		{
			return -12;
		}
		p_parse_ctx->current_parent_node = current_parent_node ;
	}
	
	return 0;
}

static inline int CallbackOnLeaveJsonBranch( int type , char *jpath , int jpath_len , int jpath_size , char *node , int node_len , char *content , int content_len , void *p )
{
	struct ParseContext	*p_parse_ctx = (struct ParseContext *)p ;
	
#if __DEBUG
	TRACE_FASTERJSON_PARAMETERS( "CallbackOnLeaveJsonBranch" )
#endif
	
	p_parse_ctx->current_parent_node = GetParentDomNode(p_parse_ctx->current_parent_node) ;
	
	return 0;
}

static inline int CallbackOnEnterJsonArray( int type , char *jpath , int jpath_len , int jpath_size , char *node , int node_len , char *content , int content_len , void *p )
{
	struct ParseContext	*p_parse_ctx = (struct ParseContext *)p ;
	
#if __DEBUG
	TRACE_FASTERJSON_PARAMETERS( "CallbackOnEnterJsonArray" )
#endif
	
	if( p_parse_ctx->root_node == NULL )
	{
		p_parse_ctx->current_parent_node = CreateDomNode( node , node_len , DOMNODE_TYPE_ARRAY , NULL , 0 ) ;
		if( p_parse_ctx->current_parent_node == NULL )
		{
			return -21;
		}
		p_parse_ctx->root_node = p_parse_ctx->current_parent_node ;
	}
	else
	{
		struct DomNode	*current_parent_node = NULL ;
		
		current_parent_node = AddChildDomNode_ARRAY( p_parse_ctx->current_parent_node , node , node_len ) ;
		if( current_parent_node == NULL )
		{
			return -22;
		}
		p_parse_ctx->current_parent_node = current_parent_node ;
	}
	
	return 0;
}

static inline int CallbackOnLeaveJsonArray( int type , char *jpath , int jpath_len , int jpath_size , char *node , int node_len , char *content , int content_len , void *p )
{
	struct ParseContext	*p_parse_ctx = (struct ParseContext *)p ;
	
#if __DEBUG
	TRACE_FASTERJSON_PARAMETERS( "CallbackOnLeaveJsonArray" )
#endif
	
	p_parse_ctx->current_parent_node = GetParentDomNode(p_parse_ctx->current_parent_node) ;
	
	return 0;
}

static inline int CallbackOnJsonLeaf( int type , char *jpath , int jpath_len , int jpath_size , char *node , int node_len , char *content , int content_len , void *p )
{
	struct ParseContext	*p_parse_ctx = (struct ParseContext *)p ;
	
	char			*endptr = NULL ;
	enum DomNodeType	node_type ;
	void			*node_data = NULL ;
	int			node_data_len ;
	char			s_buf[ DOMNODE_STRINGCACHE_SIZE ] ;
	int			s_len ;
	char			*s_ptr = NULL ;
	long			l ;
	double			d ;
	unsigned char		b ;
	struct DomNode		*p_created_node = NULL ;
	
#if __DEBUG
	TRACE_FASTERJSON_PARAMETERS( "CallbackOnJsonLeaf" )
#endif
	
	if( content[0] == '\0' )
	{
		node_type = DOMNODE_TYPE_STRING ;
		node_data = content ;
		node_data_len = 0 ;
	}
	else if( ( '0' <= content[0] && content[0] <= '9' ) || ( content[1] == '-' && '0' <= content[1] && content[1] <= '9' ) )
	{
		if( memchr( content , '.' , content_len ) )
		{
			node_type = DOMNODE_TYPE_DOUBLE ;
			d = strtod( content , & endptr ) ;
			if( endptr-content != content_len )
			{
				goto _GOTO_STRING_TYPE;
			}
			node_data = & d ;
			node_data_len = -1 ;
		}
		else
		{
			node_type = DOMNODE_TYPE_LONG ;
			l = strtol( content , & endptr , 10 ) ;
			if( endptr-content != content_len )
			{
				goto _GOTO_STRING_TYPE;
			}
			node_data = & l ;
			node_data_len = -1 ;
		}
	}
	else if( STRNICMP( content , == , "true" , content_len ) )
	{
		node_type = DOMNODE_TYPE_BOOL ;
		b = 1 ;
		node_data = & b ;
		node_data_len = -1 ;
	}
	else if( STRNICMP( content , == , "false" , content_len ) )
	{
		node_type = DOMNODE_TYPE_BOOL ;
		b = 0 ;
		node_data = & b ;
		node_data_len = -1 ;
	}
	else
	{
_GOTO_STRING_TYPE :
		node_type = DOMNODE_TYPE_STRING ;
		if( content_len < DOMNODE_STRINGCACHE_SIZE )
		{
			JSONUNESCAPE_FOLD( content , content_len , s_buf , s_len , sizeof(s_buf)-1 )
			if( s_len < -1 )
				return -31;
			node_data = s_buf ;
			node_data_len = s_len ;
		}
		else
		{
			s_ptr = (char*)malloc( content_len+1 ) ;
			if( s_ptr == NULL )
				return -41;
			JSONUNESCAPE_FOLD( content , content_len , s_ptr , s_len , content_len+1 )
			if( s_len < -1 )
				return -42;
			node_data = s_ptr ;
			node_data_len = CREATEDOMNODE_REFERENCE_AND_DELEGATION_OF_FREE ;
		}
	}
	
	if( node_type == DOMNODE_TYPE_STRING && node_data_len == 1 && content[0] == '\x7F' )
		p_created_node = CreateDomNode( node , node_len , node_type , NULL , 0 ) ;
	else
		p_created_node = CreateDomNode( node , node_len , node_type , node_data , node_data_len ) ;
	if( p_created_node == NULL )
	{
		return -43;
	}
	
	LinkChildDomNode( p_parse_ctx->current_parent_node , p_created_node );
	
	return 0;
}

struct DomNode *ParseJsonToDom( char *json )
{
	struct ParseContext	parse_ctx ;
	
	int			nret = 0 ;
	
	memset( & parse_ctx , 0x00 , sizeof(struct ParseContext) );
	nret = TravelJsonBuffer4( json , NULL , -1 , CallbackOnEnterJsonBranch , CallbackOnLeaveJsonBranch , CallbackOnEnterJsonArray , CallbackOnLeaveJsonArray , CallbackOnJsonLeaf , & parse_ctx ) ;
	SetParserLastError( nret );
	if( nret )
	{
		DestroyAllDomNodes( parse_ctx.root_node );
		return NULL;
	}
	else
	{
		return parse_ctx.root_node;
	}
}

static inline int AppendJsonDumpBuffer( char **pp_buf , int *p_buf_size , int *p_buf_len , char *src , int src_len )
{
	int	dst_len ;
	int	nret = 0 ;
	
	if( src_len == -1 )
		src_len = strlen(src) ;
	while(1)
	{
		JSONESCAPE_EXPAND( src , src_len , (*pp_buf)+(*p_buf_len) , dst_len , (*p_buf_size)-1-(*p_buf_len) )
		if( dst_len >= 0 )
		{
			(*p_buf_len) += dst_len ;
			return 0;
		}
		
		nret = ResizeDumpBuffer( pp_buf , p_buf_size ) ;
		if( nret )
			return nret;
	}
}

static inline void DumpDepth( char **pp_buf , int *p_buf_size , int *p_buf_len , int dump_options , int depth )
{
	int		i ;
	
	if( dump_options == DUMPOPTIONS_INDENTATION )
	{
		for( i = 0 ; i < depth ; i++ )
		{
			AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "\t" );
		}
	}
	
	return;
}

static inline int DumpDomNode( struct DomNode *node , char **pp_buf , int *p_buf_size , int *p_buf_len , int dump_options , int depth )
{
	char			*node_name = NULL ;
	enum DomNodeType	node_type ;
	char			*s = NULL ;
	long			l ;
	double			d ;
	unsigned char		b ;
	enum DomNodeExist	is_exist ;
	
	node_name = GetDomNodeName( node ) ;
	if( node_name )
	{
		AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "\"" );
		AppendJsonDumpBuffer( pp_buf , p_buf_size , p_buf_len , GetDomNodeName(node) , -1 );
		AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "\"" );
		if( dump_options == DUMPOPTIONS_INDENTATION )
		{
			AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , " " );
		}
		AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , ":" );
		if( dump_options == DUMPOPTIONS_INDENTATION )
		{
			AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , " " );
		}
	}
	
	node_type = GetDomNodeType( node ) ;
	switch( node_type )
	{
		case DOMNODE_TYPE_STRING :
			s = GetDomNodeData_string( node , & is_exist ) ;
			if( is_exist == DOMNODE_NODE_DATA_EXIST )
			{
				AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "\"" );
				AppendJsonDumpBuffer( pp_buf , p_buf_size , p_buf_len , s , -1);
				AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "\"" );
			}
			else
			{
				AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "null" );
			}
			break;
		case DOMNODE_TYPE_LONG :
			l = GetDomNodeData_long( node , & is_exist ) ;
			if( is_exist == DOMNODE_NODE_DATA_EXIST )
			{
				char	buf[ 40 + 1 ] ;
				int	buf_len ;
				memset( buf , 0x00 , sizeof(buf) );
				buf_len = snprintf( buf , sizeof(buf)-1 , "%ld" , l ) ;
				AppendJsonDumpBuffer( pp_buf , p_buf_size , p_buf_len , buf , buf_len );
			}
			else
			{
				AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "null" );
			}
			break;
		case DOMNODE_TYPE_DOUBLE :
			d = GetDomNodeData_double( node , & is_exist ) ;
			if( is_exist == DOMNODE_NODE_DATA_EXIST )
			{
				char	buf[ 80 + 1 ] ;
				int	buf_len ;
				memset( buf , 0x00 , sizeof(buf) );
				buf_len = snprintf( buf , sizeof(buf)-1 , "%lf" , d ) ;
				AppendJsonDumpBuffer( pp_buf , p_buf_size , p_buf_len , buf , buf_len );
			}
			else
			{
				AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "null" );
			}
			break;
		case DOMNODE_TYPE_BOOL :
			b = GetDomNodeData_bool( node , & is_exist ) ;
			if( is_exist == DOMNODE_NODE_DATA_EXIST )
			{
				if( b )
					AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "true" );
				else
					AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "false" );
			}
			else
			{
				AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "null" );
			}
			break;
		default :
			break;
	}
	
	return 0;
}

static inline int DumpDomNodes( struct DomNode *nodes , char **pp_buf , int *p_buf_size , int *p_buf_len , int dump_options , int depth )
{
	struct DomNode		*node = GetFirstDomNode( nodes ) ;
	enum DomNodeType	node_type ;
	
	while( node )
	{
		DumpDepth( pp_buf , p_buf_size , p_buf_len , dump_options , depth );
		DumpDomNode( node , pp_buf , p_buf_size , p_buf_len , dump_options , depth+1 );
		
		if( GetChildrenDomNodes( node ) )
		{
			node_type = GetDomNodeType( node ) ;
			switch( node_type )
			{
				case DOMNODE_TYPE_BRANCH :
					if( GetDomNodeName(node) )
					{
						if( dump_options == DUMPOPTIONS_INDENTATION )
						{
							AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "\n" );
						}
						DumpDepth( pp_buf , p_buf_size , p_buf_len , dump_options , depth );
					}
					AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "{" );
					if( dump_options == DUMPOPTIONS_INDENTATION )
					{
						AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "\n" );
					}
					break;
				case DOMNODE_TYPE_ARRAY :
					if( GetDomNodeName(node) )
					{
						if( dump_options == DUMPOPTIONS_INDENTATION )
						{
							AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "\n" );
						}
						DumpDepth( pp_buf , p_buf_size , p_buf_len , dump_options , depth );
					}
					AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "[" );
					if( dump_options == DUMPOPTIONS_INDENTATION )
					{
						AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "\n" );
					}
					break;
				default :
					break;
			}
			
			DumpDomNodes( GetChildrenDomNodes(node) , pp_buf , p_buf_size , p_buf_len , dump_options , depth+1 );
			
			switch( node_type )
			{
				case DOMNODE_TYPE_BRANCH :
					DumpDepth( pp_buf , p_buf_size , p_buf_len , dump_options , depth );
					AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "}" );
					break;
				case DOMNODE_TYPE_ARRAY :
					DumpDepth( pp_buf , p_buf_size , p_buf_len , dump_options , depth );
					AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "]" );
					break;
				default :
					break;
			}
		}
		
		node = GetNextDomNode( node ) ;
		if( node )
		{
			if( dump_options == DUMPOPTIONS_INDENTATION )
			{
				AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , " ,\n" );
			}
			else
			{
				AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "," );
			}
		}
		else
		{
			if( dump_options == DUMPOPTIONS_INDENTATION )
			{
				AppendDumpBuffer( pp_buf , p_buf_size , p_buf_len , "\n" );
			}
		}
	}
	
	return 0;
}

char *DumpDomToJson( struct DomNode *root , int dump_options )
{
	char		*p_buf = NULL ;
	int		buf_size ;
	int		buf_len ;
	int		nret = 0 ;
	
	if( root == NULL )
		return NULL;
	
	nret = CreateDumpBuffer( & p_buf , & buf_size , & buf_len ) ;
	if( nret )
		return NULL;
	
	nret = DumpDomNodes( root , & p_buf , & buf_size , & buf_len , dump_options , 0 ) ;
	if( nret )
	{
		free( p_buf );
		return NULL;
	}
	
	return p_buf;
}

